package icu.zhhll.local;


import icu.zhhll.util.time.DatesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * 线程安全
 * 过期缓存
 *
 * @author zh
 * @date 2022/10/10 17:02
 */
public class ExpireCache {
    private static final Logger LOGGER = LoggerFactory.getLogger("cache");
    // 数据
    private static final Map<String, Object> CACHE = new ConcurrentHashMap<>();
    // 过期时间
    private static final Map<String, Long> EXPIRE_TIME = new ConcurrentHashMap<>();


    // 过期数据惰性删除时间 单位毫秒
    private static final long EXPIRE_CHECK_UP_TIMEOUT = 2 * 60 * 60 * 1000;

    // 过期时间  单位秒
    // 数据过期最大时间
    private static final int DATA_MAX_TIMEOUT = 60 * 60;

    // 数据过期最小时间
    private static final int DATA_MIN_TIMEOUT = 10 * 60;
    // 异常数据存储最大时间
    private static final int EXCEPTION_DATA_MAX_TIMEOUT = 10;

    // 空数据存储最大时间
    private static final int EMPTY_DATA_MAX_TIMEOUT = 100;
    // 存储空对象
    private static final Object DEFAULT_OBJ = new Object();

    private ExpireCache() {
        throw new IllegalStateException("不需要实例化");
    }

    /**
     * 清空缓存
     */
    public static void clearAll() {
        EXPIRE_TIME.clear();
        CACHE.clear();
    }

    /**
     * 删除某个缓存
     *
     * @param key
     */
    public static void clear(String key) {
        if (EXPIRE_TIME.containsKey(key) && EXPIRE_TIME.get(key) < System.currentTimeMillis()) { // 过期了
            checkUpExpireData();
        }
        CACHE.remove(key);
    }

    /**
     * 缓存，如果为空，则缓存时间随机范围为[0,100]秒；若不为空，缓存时间为[10,60]分钟
     * <p>
     * 如果不想使用默认，则可以使用
     *
     * @param key
     * @param data
     */
    public static void put(String key, Object data) {
        put(key, data, DATA_MIN_TIMEOUT, DATA_MAX_TIMEOUT, EMPTY_DATA_MAX_TIMEOUT, TimeUnit.SECONDS);
    }

    /**
     * 缓存，可自定义缓存时间  单位s
     *
     * @param key        缓存key
     * @param data       缓存内容
     * @param baseSecond 缓存最小时间
     * @param maxSecond  缓存最大时间
     */
    public static void put(String key, Object data, int baseSecond, int maxSecond
    ) {
        put(key, data, baseSecond, maxSecond, EMPTY_DATA_MAX_TIMEOUT);
    }

    /**
     * 缓存，可自定义缓存时间  单位s
     *
     * @param key            缓存key
     * @param data           缓存内容
     * @param baseSecond     缓存最小时间
     * @param maxSecond      缓存最大时间
     * @param emptyMaxSecond 空值缓存时间
     */
    public static void put(String key, Object data, int baseSecond, int maxSecond, int emptyMaxSecond
    ) {
        put(key, data, baseSecond, maxSecond, emptyMaxSecond, TimeUnit.SECONDS);
    }


    /**
     * 缓存，可自定义缓存时间
     *
     * @param key          缓存key
     * @param data         缓存内容
     * @param baseTime     缓存最小时间
     * @param maxTime      缓存最大时间
     * @param emptyMaxTime 空值缓存时间
     * @param timeUnit     单位
     */
    public static void put(String key, Object data, int baseTime, int maxTime, int emptyMaxTime
            , TimeUnit timeUnit) {

        if (Objects.isNull(data)) {
            putEmpty(key, emptyMaxTime, timeUnit);
        } else {
            // 随机生成分钟数，[10,60]
            int second = ThreadLocalRandom.current().nextInt(maxTime - baseTime) + baseTime;
            putData(key, data, second, timeUnit);
        }
    }

    /**
     * 存储异常值
     *
     * @param key
     */
    public static void putException(String key) {
        LOGGER.info("{}存储异常值", key);
        // 随机生成秒数
        int seconds = ThreadLocalRandom.current().nextInt(EXCEPTION_DATA_MAX_TIMEOUT);
        putData(key, DEFAULT_OBJ, seconds);

    }

    /**
     * 存储空值
     *
     * @param key
     */
    public static void putEmpty(String key) {
        // 随机生成秒数,空值过期时间短
        int seconds = ThreadLocalRandom.current().nextInt(EMPTY_DATA_MAX_TIMEOUT);
        putEmpty(key, seconds, TimeUnit.SECONDS);

    }

    /**
     * 缓存空值
     *
     * @param key
     * @param emptyTime 缓存最大时间
     * @param timeUnit  单位
     */
    public static void putEmpty(String key, int emptyTime, TimeUnit timeUnit) {
        LOGGER.info("{}存储空值", key);
        // 随机生成秒数,空值过期时间短
        int seconds = ThreadLocalRandom.current().nextInt(emptyTime);
        putData(key, DEFAULT_OBJ, seconds, timeUnit);
    }

    /**
     * 默认缓存秒
     *
     * @param key
     * @param data
     * @param expire
     */
    private static void putData(String key, Object data, int expire) {
        // 过期时间
        putData(key, data, expire, TimeUnit.SECONDS);
    }

    /**
     * 缓存
     *
     * @param key
     * @param data
     * @param expire   时间
     * @param timeUnit 单位
     */
    private static void putData(String key, Object data, int expire, TimeUnit timeUnit) {
        // 过期时间
        long expireTime = System.currentTimeMillis() + timeUnit.toMillis(expire);
        EXPIRE_TIME.put(key, expireTime);
        CACHE.put(key, data);
    }

    /**
     * 只会取到没有过期的数据
     *
     * @param key
     * @return
     */
    public static Object get(String key) {
        if (EXPIRE_TIME.containsKey(key) && CACHE.containsKey(key)
                && EXPIRE_TIME.get(key) > System.currentTimeMillis() // 没有过期
        ) {
            LOGGER.info("{}存在缓存,过期时间{}", key, DatesUtil.FORMAT_NORMAL_DATETIME.format(EXPIRE_TIME.get(key)));
            return CACHE.get(key);
        }
        if (EXPIRE_TIME.containsKey(key) && EXPIRE_TIME.get(key) < System.currentTimeMillis()) { // 过期了
            checkUpExpireData();
        }
        return null;
    }

    /**
     * 清理过期数据
     */
    private static void checkUpExpireData() {
        int count = 0;
        for (Map.Entry<String, Long> entry : EXPIRE_TIME.entrySet()) {
            // 过期很长时间的数据进行清理
            if (entry.getValue() + EXPIRE_CHECK_UP_TIMEOUT < System.currentTimeMillis()) {
                EXPIRE_TIME.remove(entry.getKey());
                CACHE.remove(entry.getKey());
                count++;
            }
        }
        LOGGER.info("清理过期数据{}条", count);
    }

    /**
     * 强制获取，不关心是否超时，只要缓存中有就会取到
     *
     * @param key
     * @return
     */
    public static Object forceGet(String key) {
        if (CACHE.containsKey(key)) {
            return CACHE.get(key);
        }
        return null;
    }

    /**
     * 尝试获取 只要缓存中存在就返回true
     *
     * @param key
     * @return
     */
    public static boolean tryGet(String key) {
        return CACHE.containsKey(key);
    }

}
